'''
使用方法:
调用 run_app() 函数
'''

import aiohttp
import asyncio
import re
import os
import random
import common
from common.strtools import hex_add_0x
from binascii import a2b_hex,b2a_hex,a2b_base64,b2a_base64
import base64
from hexbytes import HexBytes

from aiohttp import web
from collections import namedtuple
import functools
import json
from appenv import AppEnv
from aiohttptools import OK,FAILED

import routes

from ipfstools import IPFSHelper
from encrypter import MyAES,MyMD5
from abe import MyABE

from db import DBHelper

class UIBackend:
    def __init__(self,app_env:AppEnv):
        self.env = app_env
        self.ip = common.args.ClientUIBackendIP
        self.port = common.args.ClientUIBackendPort
        self.RouteParam = namedtuple(
            "RouteParam", ["method", "path", "handler"])
        self.route_param_list = []
        # MD5
        self.md5 = MyMD5()

    def run_app_in_loop(self,loop):
        asyncio.set_event_loop(loop)
        loop.run_until_complete(self.init_app(loop))

    def run_app(self):
        # loop = asyncio.get_event_loop()
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        loop.run_until_complete(self.init_app(loop))
        loop.run_forever()
        pass

    async def init_app(self, loop):
        app = web.Application(loop=loop)
        self._build_controllers()
        # self._init_add_static(app)
        self._init_add_route(app)
        srv = await loop.create_server(app.make_handler(), self.ip, self.port)
        print('UI backend started at {}:{}...'.format(self.ip, self.port))
        return srv

    '''
    以下为初始化函数
    '''

    def _init_add_route(self, app):
        '''
        为webapp设置路由
        '''
        for ele in self.route_param_list:
            app.router.add_route(ele.method, ele.path, ele.handler)

    
    def controller(self, method, path):
        '''
        controller装饰器，模仿springboot
        '''
        def wrapper(func):
            f = func
            self.route_param_list.append(self.RouteParam(
                method=method, path=path, handler=f))
            return f            
        return wrapper

    def log(self,request):
        common.print_msg("ui_backend",request)

    
    def _build_controllers(self):
        '''
        controllers
        '''
        @self.controller("POST","/sign_up")
        async def sign_up(request:web.Request):
            self.log(request)
            request_json = await request.json()
            keyword = request_json.get("keyword",None)
            if keyword:
                acct_addr = self.env.sign_up_with_keyword(keyword)
                return web.json_response({
                    "account_address":acct_addr
                })
            raise web.HTTPBadRequest()

        @self.controller("POST","/sign_in")
        async def sign_in(request:web.Request):
            self.log(request)
            request_json = await request.json()
            priv_key_str = request_json.get("account_private_key",None)
            if priv_key_str:
                self.env.sign_in_with_key_str(priv_key_str)
            else:
                self.env.sign_in_with_stored_key(
                    request_json["account_address"],
                    request_json["keyword"]
                )
            return web.json_response({
                "account_address":self.env.acct_address
            })

        @self.controller("GET","/acct_address")
        async def get_acct_address(request:web.Request):
            self.log(request)
            address = self.env.get_acct_address()
            # return web.Response(body=address)
            return web.json_response({"address":address})

        @self.controller("POST","/DU_send_registration_request")
        async def DU_send_request(request:web.Request):
            self.log(request)
            self.env._check_log_in()
            request_json = await request.json()
            target_ip = request_json["target_ip"]
            ex_msg_bytes = request_json.get("extra_message",None)
            if ex_msg_bytes is None:
                ex_msg_to_send = ""
            else:
                ex_msg_to_send = a2b_base64(ex_msg_bytes).decode("utf-8") # “我是小明”
            # target_http_address = request_json["target_http_address"] # ip:port
            http_address_get_acct = (
                # f"{target_http_address}{routes.acct_address}"
                f"http://{target_ip}:{common.args.ClientWebPort}{routes.acct_address}"
            )
            http_address_send_req = (
                # f"{target_http_address}{routes.file_sharing_registration}"
                f"http://{target_ip}:{common.args.ClientWebPort}{routes.file_sharing_registration}"   
            )   
            await self.env.the_third_step_in_the_paper(
                http_address_get_acct,
                http_address_send_req,
                ex_msg_to_send,
            )
            return OK()

        @self.controller("GET","/all_registrations")
        async def get_all_registrations(request:web.Request):
            self.log(request)
            self.env._check_log_in()
            dct:dict = self.env.received_requset_dct
            # +++++++++++++++++++++
            # [ls["request"]]
            # send_info = {
            #     "address":self.my_address,
            #     "DH_PK":self.my_pub_key,
            #     "ex_msg":ex_msg_to_send
            # }
            # +++++++++++++++++++++
            """
            {
                "address":发送者 address,
                "time":请求时间,
                "ex_msg":额外信息
            }
            """
            ls = list()
            for k,v in dct.items():
                ls.append({"address":k,"time":v["time"],"ex_msg":v["request"]["ex_msg"]})
            ls.sort(key=lambda x:x["time"],reverse=True)
            if len(ls)>0:
                _latest = ls[0]
                return web.json_response(_latest)
            else:
                return web.Response(body="")

        @self.controller("GET","/all_DOs")
        async def get_all_DOs(request:web.Request):
            self.log(request)
            self.env._check_log_in()
            all_DO_contract_records = DBHelper.get_all_DO_contracts()
            ls = [r.account_address for r in all_DO_contract_records]
            return web.json_response(ls)
            

        # TODO 这里比较麻烦，还有一个 ABE 秘钥生成的步骤
        @self.controller("POST","/handle_registrations")
        async def handle_registrations(request:web.Request):
            self.log(request)
            self.env._check_log_in()
            requestJson = await request.json()

            acct_addr = hex_add_0x(requestJson["address"])
            agree = requestJson["agree"]
            # 获取选择的属性列表
            # attrs:list = requestJson["attributes"] 
            # 防止有中文，传来的时候是经过 base64 编码的--->所以要先解码
            attrs:list = [a2b_base64(attr).decode("utf-8") for attr in requestJson["attributes"]]

            if agree is True:
                # resp = self.env.
                # +++++++
                req = self.env.received_requset_dct[acct_addr]
                # +++++++++++++++++++++
                # [reqJson]
                # send_info = {
                #     "address":self.my_address,
                #     "DH_PK":self.my_pub_key,
                #     "ex_msg":ex_msg_to_send
                # }
                # +++++++++++++++++++++
                reqJson = req["request"] 
                url = req["http_address_for_return"]
                _acct_addr = hex_add_0x(reqJson["address"])
                assert _acct_addr == acct_addr
                await self.env.the_fifth_step_in_the_paper(
                    url,reqJson,attrs
                )
                # #删除已经处理的
                del self.env.received_requset_dct[hex_add_0x(acct_addr)]

                # #+++++++++++
                # # TODO 调用智能合约存账户
                # #+++++++++++
                _w3DO = self.env.new_MyWeb3DO()
                _w3DO.addUser(acct_addr)
                # #+++++++++++

                # 这里返回值应该没用
                return web.json_response({
                    "address":acct_addr,
                    "agree":True
                })
            else:
                return web.json_response({
                    "address":acct_addr,
                    "agree":False
                })
            pass

        # @self.controller("POST","/deploy_DO_contract")
        # async def deploy_DO_contract(request:web.Request):
        #     self.log(request)
        #     self.env._check_log_in()
        #     # requestJson = await request.json()
        #     self.env.deploy_DO_contract()
        #     return OK()

        # @self.controller("POST","/deploy_DU_contract")
        # async def deploy_DU_contract(request:web.Request):
        #     self.log(request)
        #     self.env._check_log_in()
        #     # requestJson = await request.json()
        #     self.env.deploy_DU_contract()
        #     return OK()

        @self.controller("POST","/upload_file")
        async def upload_file(request:web.Request):
            self.log(request)
            self.env._check_log_in()
            # 第 8 步
            requestJson = await request.json()
            # =====================
            # 随机的字符串(用上MD5)，用于生成 AES 的 key
            random_str = self.md5.encrypt(str(random.randint(0,1<<63)))
            _AES = MyAES(random_str) 
            # TODO ABE 这句是否应该封装到 self.env 里？？
            _ABEOwnerKeyRecord = DBHelper.get_ABEOwnerKey(self.env.acct_address) 
            # TODO
            # =====================
            file_name = a2b_base64(requestJson["file_name"]).decode("utf-8")
            keywords:list = [a2b_base64(ele).decode("utf-8") for ele in requestJson["keywords"]]
            keywords.append(file_name)
            policy:list = [a2b_base64(ele).decode("utf-8") for ele in requestJson["policy"]]
            # 在 policy 中增加最高权限的属性 __OWNER__，使数据拥有者也可以解码
            policy.append(common.args.HighestAuthorityAttr)

            # content =  a2b_base64(requestJson["content"])
            content_b64_str =  requestJson["content"]
            encrypted_content:bytes = _AES.encrypt(content_b64_str) # 文件加密

            file_location = IPFSHelper.upload_file(file_name,encrypted_content)
            file_location_str:str = json.dumps(file_location) # 文件location 要用 ABE 
            # file_location_bytes:bytes = file_location_str.encode("utf-8")
            encrypted_file_location_bytes:bytes = _AES.encrypt(file_location_str)
            encrypted_file_location:str = encrypted_file_location_bytes.decode("utf-8")


            encrypted_AES_key:str = MyABE.encrypt(
                public_key = _ABEOwnerKeyRecord.public_key,
                msg = _AES.skey,
                access_policy = policy
            )

            txn_json = {
                "owner_address":self.env.acct_address,
                "the_key":encrypted_AES_key,
                "file_location":encrypted_file_location
            }
            txn_data_str:str = json.dumps(txn_json)
            txn_data_bytes = txn_data_str.encode("utf-8")

            # 把加密后的数据存到 transaction 之中
            txn_hash:HexBytes = self.env.send_transaction(txn_data_bytes)

            # TODO 智能合约
            # +++++++++++++++++++++++++++++++++++++++++
            _txn_hash_bytes = bytes(txn_hash) # bytes32
            _keywords = [self.md5.encrypt(k) for k in keywords] #32字节
            _file_name = file_name # 无要求
            _w3DO = self.env.new_MyWeb3DO()
            _w3DO.addIndex(_keywords,_txn_hash_bytes,_file_name)
            # +++++++++++++++++++++++++++++++++++++++++

            _ret = {
                "transaction_id":txn_hash.hex(),
                "file_location":file_location
            }
            common.print_msg(_ret)
            return web.json_response(_ret)

        @self.controller("POST","/download_file")
        async def download_file(request:web.Request):
            self.log(request)
            self.env._check_log_in()

            requestJson = await request.json()
            # TODO 下载 bytes --> ipfs 
            txn_hash = requestJson["transaction_id"]
            txn_data_bytes:bytes = self.env.get_transaction_data(txn_hash)
            txn_data_str:str = txn_data_bytes.decode("utf-8")
            txn_json:dict = json.loads(txn_data_str)
            """
            txn_json = {
                "owner_address":text,
                "the_key":ciphertext,
                "file_location":ciphertext
            }
            """
            # TODO ABE 这句是否应该封装到 self.env 里？？
            _ABEUserKeyRecord = DBHelper.get_ABEUserKey(
                master_acct_addr = txn_json["owner_address"],
                user_acct_addr = self.env.acct_address
            ) 

            _AES_key = MyABE.decrypt(
                public_key=_ABEUserKeyRecord.public_key, 
                private_key=_ABEUserKeyRecord.private_key, 
                ciphertext=txn_json["the_key"]
            )

            _AES = MyAES(_AES_key)
            encrypted_file_location_bytes:bytes = txn_json["file_location"].encode("utf-8")
            file_location_str:str = _AES.decrypt(encrypted_file_location_bytes)

            # ++++ tmp ++++
            # file_location_str = txn_data.decode("utf-8")
            file_location = json.loads(file_location_str)
            encrypted_content = IPFSHelper.download_file_with_location(file_location)
            content_b64_str = _AES.decrypt(encrypted_content)
            # b64_content = b2a_base64(content)
            b64_file_name_bytes = base64.b64encode(file_location["Name"].encode("utf-8"))
            # +++++++++++++
            # TODO 
            return web.json_response({
                "file_name":b64_file_name_bytes.decode("utf-8"),
                "content":content_b64_str
            })

        @self.controller("GET","/test_abe")
        async def test_abe(request:web.Request):
            self.log(request)
            self.env._check_log_in()
            policy = ['type1', 'type2', 'type3', '__OWNER__']
            msg = "12345"
            enc = MyABE.encrypt(self.env.ABE_public_key,msg,policy)
            msg = MyABE.decrypt(self.env.ABE_public_key,
                self.env.ABE_owner_priv_key,enc)
            return web.Response(body=msg)


        @self.controller("POST","/search")
        async def search(request:web.Request):
            self.log(request)
            self.env._check_log_in()
            requestJson = await request.json()
            DO_acct_addr = requestJson["account_address"]
            tokens = [a2b_base64(ele).decode("utf-8") for ele in requestJson["tokens"]]
            # 获取 DO 的 contract
            record = DBHelper.get_contract_with_acct_addr(DO_acct_addr,is_DO_contract=True)
            contract_address = record.contract_address
            # TODO 智能合约 搜索
            _tokens = [self.md5.encrypt(ele) for ele in tokens]
            _w3DU = self.env.new_MyWeb3DU()
            flag_succeed:bool = _w3DU.dataSearch(_tokens,contract_address)
            if flag_succeed:
                result = _w3DU.getResult(contract_address)
                return web.json_response(result)
            else:
                return web.Response(body="")

        
        @self.controller("GET","/all_file_names")
        async def get_all_file_names(request:web.Request):
            # TODO 有问题
            self.log(request)
            self.env._check_log_in()
            _w3DO = self.env.new_MyWeb3DO()
            file_names = _w3DO.getFilenames()
            return web.json_response(file_names)  

        # @self.controller("POST","/delete_file")
        # async def delete_file(request:web.Request):
        #     requestJson = await request.json()
        #     # ???? TODO 怎么删除
        #     # TODO 
        #     return OK()

        # @self.controller("POST","/delete_keywords")
        # async def delete_keywords(request:web.Request):
        #     requestJson = await request.json()
        #     # TODO
        #     return OK()

        # @self.controller("POST","/delete_ath_user")
        # async def delete_ath_user(request:web.Request):
        #     requestJson = await request.json()
        #     # TODO 
        #     return OK()

        # @self.controller("POST","/withdraw_eth_coin")
        # async def withdraw_eth_coin(request:web.Request):
        #     self.log(request)
        #     requestJson = await request.json()
        #     # TODO 
        #     return OK()
        

        


        

        



